/*
 * dir_watcher_base.cpp
 *
 *  Created on: 2010/07/18
 *      Author: terry
 */

#include "dir_watcher_base.h"
#include <sys/inotify.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <sstream>
#include <boost/smart_ptr.hpp>

using namespace std;
using namespace boost;

///////////////////////////////////////////////////////////////////////////////
//  constructor & destructor
///////////////////////////////////////////////////////////////////////////////
dir_watcher_base::dir_watcher_base() :
    opened(false), keep_running(true)
{
    this->dir_list.clear();
}

dir_watcher_base::~dir_watcher_base()
{
    if (this->opened) {
        this->destroy();
    }
}


///////////////////////////////////////////////////////////////////////////////
//  public functions
///////////////////////////////////////////////////////////////////////////////

void dir_watcher_base::stop_watch()
{
    this->keep_running = false;
}

void dir_watcher_base::open() throw (op_error)
{
    if (this->opened) {
        this->destroy();
    }

    // inotify_init
    int _fd = inotify_init();
    if (_fd < 0) {
        throw op_error("fail to initialize inotify", errno);
    } else {
        this->fd = _fd;
        this->opened = true;
    }
}

void dir_watcher_base::destroy()
{
    int retval = close(this->fd);

    if (retval < 0) {
        std::cerr << "fail to close inotify file descriptor" << std::endl;
    }
    this->opened = false;
    this->dir_list.clear();
    this->fd = 0;
}

size_t dir_watcher_base::watched_items_count()
{
    return this->dir_list.size();
}

vector<string>& dir_watcher_base::watched_items()
{
    return this->dir_list;
}

void dir_watcher_base::add_directory(const string& dir, unsigned long mask) throw (op_error)
{
    int wd = inotify_add_watch(this->fd, dir.c_str(), mask);
    if (wd >= 0) {
        this->dir_list.push_back(dir);
    } else {
        stringstream ss;
        ss << "can not watch " << dir << "with event " << mask;
        throw op_error(ss.str().c_str(), errno);
    }
}

void dir_watcher_base::start_watch() throw (op_error)
{
    this->keep_running = true;

    while (this->keep_running && (this->watched_items_count() > 0)) {
        if (this->event_check() > 0) {
            if (read_events() < 0) {
                break;
            }
        }
    }
}

signals2::connection dir_watcher_base::connect(const signal_t::slot_type &subscriber)
{
    return this->sig.connect(subscriber);
}

///////////////////////////////////////////////////////////////////////////////
//  protected functions
///////////////////////////////////////////////////////////////////////////////
void dir_watcher_base::on_changed(shared_ptr<dir_event> evt)
{
    sig(evt);
}

///////////////////////////////////////////////////////////////////////////////
//  private functions
///////////////////////////////////////////////////////////////////////////////
int dir_watcher_base::event_check()
{
    fd_set rfds;
    FD_ZERO (&rfds);
    FD_SET (this->fd, &rfds);
    timeval tv;
    tv.tv_sec = 5;
    tv.tv_usec = 0;
    return select(FD_SETSIZE, &rfds, NULL, NULL, &tv);
}

int dir_watcher_base::read_events()
{
    const int SIZE = 16384;
    char buffer[SIZE];
    ssize_t buffer_i;
    struct inotify_event *pevent;
    ssize_t r;
    size_t event_size;
    int count = 0;

    r = read(fd, buffer, SIZE);
    if (r <= 0)
        return r;
    buffer_i = 0;
    while (buffer_i < r) {
        /* Parse events and queue them. */
        pevent = (struct inotify_event *) &buffer[buffer_i];
        shared_ptr<dir_event> evt(new dir_event()) ;
        evt->file_name = pevent->name;
        evt->mask = pevent->mask;
        this->on_changed(evt);
        event_size = offsetof (struct inotify_event, name) + pevent->len;
        buffer_i += event_size;
        count++;
    }
    return count;

}



